package archive

import (
	"fmt"
	"gitee.com/go-errors/errors"
	"io"
	"log"
	"os"

	"github.com/golang/snappy"
	"github.com/klauspost/compress/gzip"
	"github.com/klauspost/compress/zstd"
	"github.com/ulikunitz/xz"
)

type Decoder struct {
	path         string    //文件名
	stream       io.Reader //文件流
	compressKind Kind      //压缩类型
	archiveKind  Kind      //打包类型
	trimRootPath bool      //解压时去掉最顶层的文件夹名称
	saveTo       string    //保存路径，如果是 TAR 或者 ZIP，必须是目录

	verbose bool
}

//New 初始化文档解码器
func New() *Decoder {
	return &Decoder{}
}

func Source(path string) *Decoder {
	return New().Source(path)
}

func Read(r io.Reader, path string) *Decoder {
	return New().Read(r).Source(path)
}

//Source 来源压缩文件路径，用来猜测压缩类型
func (d *Decoder) Source(path string) *Decoder {
	d.path = path
	return d.DirectKind(path)
}

//Source 来源流
func (d *Decoder) Read(r io.Reader) *Decoder {
	d.stream = r
	return d
}

// DirectKind 通过文件名来确定压缩类型
func (d *Decoder) DirectKind(name string) *Decoder {
	d.archiveKind, d.compressKind, _ = GetKindByExt(name)
	return d
}

//Compress 指定文档的压缩类型: 支持 TAR, ZIP, GZ, ZST, XZ, SNZ
func (d *Decoder) Compress(kind Kind) *Decoder {
	d.compressKind = kind
	if kind == ZIP {
		d.archiveKind = ZIP
	}
	return d
}

// Archive 指定文档的打包类型: 支持 ZIP 和 TAR
func (d *Decoder) Archive(kind Kind) *Decoder {
	d.archiveKind = kind
	if kind == ZIP {
		d.compressKind = ZIP
	}
	return d
}

//Tar 指定文档为tar打文档
func (d *Decoder) Tar() *Decoder {
	return d.Archive(TAR)
}

//Zip 指定文档为zip打文档
func (d *Decoder) Zip() *Decoder {
	return d.Archive(ZIP)
}

//GZ 指定文档通过gzip压缩
func (d *Decoder) GZ() *Decoder {
	return d.Compress(GZ)
}

//ZST 指定文档通过zstd压缩
func (d *Decoder) ZST() *Decoder {
	return d.Compress(ZST)
}

// XZ 指定文档通过xz压缩
func (d *Decoder) XZ() *Decoder {
	return d.Compress(XZ)
}

// SNZ 指定文档通过snappy压缩
func (d *Decoder) SNZ() *Decoder {
	return d.Compress(SNZ)
}

//Verbose 显示处理过程
func (d *Decoder) Verbose(yes ...bool) *Decoder {
	d.verbose = len(yes) == 0 || yes[0]
	return d
}

//TrimRootPath 解压时去掉最顶层的文件夹名称
func (d *Decoder) TrimRootPath(yes ...bool) *Decoder {
	d.trimRootPath = len(yes) == 0 || yes[0]
	return d
}

// SaveTo 保存路径，如果是 TAR 或者 ZIP，必须是目录
func (d *Decoder) SaveTo(saveTo string) *Decoder {
	d.saveTo = saveTo
	return d
}

// DeCompress 解压
func (d *Decoder) DeCompress(r io.Reader) (dr io.ReadCloser, err error) {
	switch d.compressKind {
	case GZ:
		return gzip.NewReader(r)
	case XZ:
		r, err = xz.NewReader(r)
	case ZST:
		r, err = zstd.NewReader(r)
	case SNZ:
		r = snappy.NewReader(r)
	case ZIP:
	default:
		return nil, fmt.Errorf("unsupport compress kind: %s", d.compressKind)
	}
	if err != nil {
		return
	}
	return io.NopCloser(r), nil
}

//Extract 解包文档
func (d *Decoder) Extract() (err error) {
	if d.verbose {
		log.Printf("extract: %s.%s to  %s", d.archiveKind, d.compressKind, d.saveTo)
	}
	if d.stream == nil && d.path == "" {
		if d.verbose {
			log.Println("miss source")
		}
		return errors.New("miss source")
	}

	var r io.ReadCloser
	if d.verbose {
		log.Printf("open source")
	}
	if d.stream != nil {
		r = io.NopCloser(d.stream)
	} else {
		r, err = os.Open(d.path)
	}
	if err != nil {
		if d.verbose {
			log.Printf("open source: %v", err)
		}
		return
	}
	defer tryClose(r)

	var dr io.ReadCloser
	if d.verbose {
		log.Printf("decompress source")
	}
	if dr, err = d.DeCompress(r); err != nil {
		if d.verbose {
			log.Printf("decompress source: %v", err)
		}
		return
	}
	defer tryClose(dr)

	if d.archiveKind == "" {
		return readToFile(dr, d.saveTo, 0644)
	}

	var next readNext
	if d.verbose {
		log.Printf("get extract mehtod")
	}
	if next, err = d.getNextFunc(d.archiveKind, dr); err != nil {
		if d.verbose {
			log.Printf("get extract mehtod: %v", err)
		}
		return
	}

	if d.verbose {
		log.Printf("extract archive")
	}
	if err = d.extractToDIR(next, d.trimRootPath, d.saveTo); err != nil {
		if d.verbose {
			log.Printf("extract archive: %v", err)
		}
	}
	return
}

//WriteTo 解压流的方法
func (d *Decoder) WriteTo() func(r io.Reader) error {
	return func(r io.Reader) error { return d.Read(r).Extract() }
}

func (d *Decoder) getNextFunc(archiveKind Kind, r io.Reader) (readNext, error) {
	switch archiveKind {
	case ZIP:
		return readZip(r), nil
	case TAR:
		return readTar(r), nil
	default:
		return nil, fmt.Errorf("unsupport archive kind: %s", archiveKind)
	}
}

func (d *Decoder) extractToDIR(nextFn readNext, trimRootPath bool, saveTo string) error {
	for {
		e, err := nextFn()
		if err != nil {
			if d.verbose {
				log.Println("break:", err)
			}
			if err == io.EOF {
				break
			}
			return err
		}

		target := getTargetFilePath(saveTo, e.Path, trimRootPath)
		if d.verbose {
			log.Printf("extract: %s", target)
		}
		if target == "" {
			continue
		}

		if e.IsDir() {
			if err := os.MkdirAll(target, e.Mode()); err != nil {
				return err
			}
			continue
		}
		if err := readToFile(e, target, e.Mode()); err != nil {
			return err
		}
	}
	return nil
}
